module Othello where

import Maybe
import Tablero
import Char
import List

data Juego = J Color Tablero
data Jugada = M Posicion | Paso
  deriving (Show, Eq)

data Arbol a = Nodo a [Arbol a] deriving Show
type ArbolJugadas = Arbol ([Jugada], Juego)

type Valuacion = Juego -> Double

instance Show Juego where
  show (J turno tablero) = "\n--Juega el " ++ show turno ++ "--\n" ++ show tablero

arbolDeJugadas :: Juego -> ArbolJugadas
arbolDeJugadas j = Nodo ([], j) $ zipWith agmov movs hijos
  where agmov m (Nodo (ms, r) hs) = Nodo ((m:ms), r) (map (agmov m) hs)
        movsJuegos = jugadasPosibles j
        movs = map fst movsJuegos
        hijos = map (arbolDeJugadas . snd) movsJuegos

-- ** Función auxiliar **
-- Decide si una jugada es válida para un juego determinado según las reglas
-- del Othello, o sea, si la posicion donde se va a jugar esta dentro de los limites del tablero,
-- no contiene ninguna ficha, y al jugar se come al menos una ficha del contrincante.
jugadaValida :: Jugada -> Juego -> Bool
jugadaValida Paso _ = True
jugadaValida (M pos) (J color tablero) = posicionValida pos && (contenido pos tablero) == Nothing && comeFicha
	where tableroConFicha = poner pos color tablero
	      comeFicha = not (null (posicionesAInvertir pos tableroConFicha))

-- Ejercicio 7: jugar
-- Realiza la jugada indicada (si es válida). Si la jugada es Paso, invierte
-- el color del jugador y deja igual el tablero. En cambio, si pone una ficha,
-- además de invertir el color, pone la ficha e invierte las posiciones que 
-- afecta el poner la ficha indicada usando las funciones del módulo Tablero.
jugar :: Jugada -> Juego -> Maybe Juego
jugar Paso (J color tablero) = Just (J (colorContrario color) tablero)
jugar (M pos) (J color tablero) 
    | jugadaValida jugada juego = Just (J (colorContrario color) (invertirTodas (posicionesAInvertir pos tableroConFicha) tableroConFicha))
	| otherwise = Nothing
	where juego = (J color tablero)
	      jugada = (M pos)
	      tableroConFicha = poner pos color tablero

-- Ejercicio 8: jugadas posibles
-- Calcula las jugadas posibles y el tablero resultante al realizar la jugada
-- verificando para cada posición del tablero si es válida la jugada. Si no
-- hay jugadas válidas, la única jugada posible es Paso. En caso de haber,
-- las agrega a la lista de posibles y calcula el nuevo juego para cada una.
jugadasPosibles :: Juego -> [(Jugada,Juego)]
jugadasPosibles juego 
	| null posibles = [(Paso,fromJust (jugar Paso juego))]
	| otherwise = posibles
	where posibles = [ (M (x,y),fromJust (jugar (M (x,y)) juego)) | x <- ['a'..'h'], y <- [1..8], jugadaValida (M (x,y)) juego]

-- Ejercicio 9: fold para árbol
-- Mapea la función foldArbol currificada a cada subárbol y al resultado le
-- aplica la función dada según los valores obtenidos por la recursión y el
-- valor del nodo evaluado.
foldArbol :: (a -> [b] -> b) -> Arbol a -> b
foldArbol f (Nodo valor subarboles) = f valor (map (foldArbol f) subarboles) 

-- Ejercicio 10: Devuelve el arbol k-ario pasado como parametro,
-- podado a un cierto nivel n. Se usa una funcion auxiliar para generar
-- una funcion recursiva dedicada a ese arbol, que dado un entero
-- devuelve el arbol podado a ese nivel. Luego se llama a esa funcion
-- con el parametro n.
-- Esta funcion auxiliar se genera aplicando la funcion fabricaArbolPodado
-- por medio del foldArbol a todos sus nodos. El fold "comprime" un nodo en el arbol
-- en una funcion recursiva, armando el resultado de la funcion para ese nivel en la recursion.
-- Como conoce las funciones generadoras de sus hijos, de ser necesario las llama
-- decrementando el parametro que especifica el nivel de la poda por un nivel,
-- y de ser menor o igual al caso base devuelve el resultado correspondiente al mismo.
podar :: Int -> Arbol a -> Arbol a
podar n arbol = (foldArbol (fabricaArbolPodado) arbol) n  

-- ** Función auxiliar **
-- Esta funcion toma el valor de un nodo, y las funciones generadoras
-- de los arboles podados de los hijos. Luego devuelve una funcion que dado n,
-- devuelve el arbol podado a nivel n. Si n es menor a dos,
-- sabe que tiene que devolver la raiz y la lista de hijos vacia,
-- en caso que n > 1, devuelve la raiz, y las funciones generadoras de hijos podados evaluadas en n-1.
fabricaArbolPodado :: a -> [(Int -> Arbol a)] -> (Int -> Arbol a)
fabricaArbolPodado valor xs = (\n -> if n > 1 then arbol n else (Nodo valor []))
	where arbol n = (Nodo valor (map (\f -> f (n-1)) xs))

-- Ejercicio 11: mejor jugada (+minimax)
-- Devuelve la primer jugada de la lista de jugadas devuelta por minimax para
-- llegar al juego más favorable hacia el jugador actual a futuro
mejorJugada :: Valuacion -> ArbolJugadas -> Jugada
mejorJugada val jugadas = head (snd (minimax valuacionOthello jugadas))

-- ** Función auxiliar **
-- 
minimax :: Valuacion -> ArbolJugadas -> (Double,[Jugada])
minimax valuacion arbol = foldArbol 
	-- la siguiente funcion 'pliega' un nodo del arbol en el fold, 
	-- elijiendo la jugada optima entre sus hijos y devolviendola
	-- la secuencia optima a partir de esa jugada junto con su puntaje
	-- :: ([Jugada], Juego) -> [ (Double,Jugada) ] -> (Double,Jugada)
	(\ (jugadasHechas,juego) jugadasPosibles ->
		if null jugadasPosibles
		then (valuacion juego,[last jugadasHechas])
		else (
			(\cmp -> (fst (maxval jugadasPosibles cmp) , (maybeLast jugadasHechas)++(snd (maxval jugadasPosibles cmp))) ) 
			( if (esMiTurno jugadasHechas) then (>) else (<) )
		)
	) 
	arbol

-- ** Función auxiliar **
-- dada una secuencia de jugadas indica si es el turno
-- de la misma persona que realizo la primera jugada
esMiTurno :: [Jugada] -> Bool
esMiTurno jugadasHechas = (length jugadasHechas) `mod` 2 == 0

-- ** Función auxiliar **
-- dada una lista, devuelve la lista con el ultimo elemento,
-- si este existe, y si no una lista vacia
maybeLast :: [a] -> [a]
maybeLast [] = []
maybeLast ls = [last ls]

-- ** Función auxiliar **
-- dada una lista de sucesiones de jugadas optimas, con su respectiva valuacion,
-- devuelve la mejor de ellas segun una funcion de comparacion
maxval :: [ (Double,[Jugada]) ] -> (Double -> Double -> Bool) -> (Double,[Jugada])
maxval ls mayor = foldr1 (\val max -> if (mayor (fst val) (fst max)) then val else max) ls

-- Ejercicio 12: ganador
-- Devuelve el ganador del juego, si es que terminó. Para saber quién ganó
-- se fija si ambos jugadores pasan con el estado actual del tablero. En
-- ese caso, cuenta qué jugador tiene más fichas y decide el ganador.
ganador :: Juego -> Maybe Color
ganador (J color tablero)
	| pasa (jugadasPosibles juego) && pasa (jugadasPosibles (J (colorContrario color) tablero)) = 
		if contar Blanco > contar Negro then Just Blanco else Just Negro
	| otherwise = Nothing
	where juego = (J color tablero)
	      contar color = contarFichas color tablero
	      pasa jugadas = length jugadas == 1 && fst (head jugadas) == Paso

-- ** Función auxiliar **
-- Devuelve la cantidad de fichas de un color en el tablero dado. Para ello,
-- pide la longitud de una lista que contiene un elemento por cada ficha (del 
-- color indicado) en el tablero.
contarFichas :: Color -> Tablero -> Int
contarFichas color tablero = length [ 1 | x <- ['a'..'h'], y <- [1..8], contenido (x,y) tablero == Just color ]

-- Ejercicio 13: valuación Othello
-- Calcula la valuación dada
valuacionOthello :: Valuacion
valuacionOthello (J color tablero)
	| ganador juego == Just color = 1.0
	| ganador juego /= Nothing = -1.0
	| otherwise = 2.0*m/tot-1
	where juego = (J color tablero)
	      m = fromIntegral (contarFichas color tablero)
	      tot = m + fromIntegral (contarFichas (colorContrario color) tablero)
